home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Software Vault: The Diamond Collection
/
The Diamond Collection (Software Vault)(Digital Impact).ISO
/
cdr44
/
forc_c.zip
/
C.TXT
Wrap
Text File
|
1995-01-26
|
32KB
|
779 lines
Linking C functions with your Force programs
------------------------------------------------
Overview
--------
When writing complex programs or applications, you may find that you
need features that Force does not provide in the Force library, yet do
not want to give up the benefits that Force provides for execution
time and size. This is where third party libraries may come to your
rescue. Third party libraries are code libraries of functions and
procedures written by a company other than SEC, usually for Force
applications. However, because of Force's flexibility, you may use
thousands of third party libraries that were written for Microsoft or
Borland C compilers. Third party libraries often cover whole suites
of topics, or limit themselves to offering all possible features of
one specific topic. Common third party libraries provide support for
computer graphics, accounting, higher-level mathematics, and other
topics.
The third party libraries written specifically for Force usually ship
with their own Force header files for the functions and procedures in
their library, but third party libraries written for C compilers or
assemblers do not.
You may also already have functions that you have written in C or
assembler, but want to use in your Force programs. For whatever
reason that you decide to use a third party or C function library with
your application, use this section as a guideline for making the
library work with you, not against you.
Who's Compatible, and Who's not
At the time of this writing, only two major C compilers are
compatible with the Force compiler. Those are Microsoft C versions
5.0 and 6.0, and Borland's Turbo C versions 2.0 through 4.02.
Microsoft's current C/C++ compiler (version 7.0) generates compatible
code, but Force is not compatible with its runtime library. Other,
less well-known C compilers may or may not be compatible with Force
depending on whether or not they generate standard Intel object code
.OBJ files.
Understanding the basis of the lack of compatibility makes a number
of other facts readily sensible.
Microsoft incompatibility is not marked, and in many cases MS C is
actually compatible in later versions. FORCE is completely compatible
with Microsoft C versions up through 6.x (remember that you have to
link in the appropriate MICROx.OBJ file if MS runtime library
functions are called). In MS C version 7.x, it appears that the
actual compiler code and much of the runtime library code is
compatible. Incompatibilities center around the use of DBL numbers
(floats) in MS code as Microsoft's handling of these numbers departed
from strict IEEE conventions as of MS version 7.0; consequently, MS C
versions 7.x and 8.x (VC) produce C code that should be compatible
with FORCE as long as floats are not used in the C code or in the MS
runtime library functions called. This problem is currently under
examination. There are thus indications that MS C versions 7.x and 8.x
are in fact compatible if the "alternate math" compiler switch and
MS C libraries are used, but this has not been tested completely at
the time of this writing.
In any event, it appears from experiment that MS Quick C version 2.x
is also compatible with FORCE, and other C compilers may be as well.
Please notify SEC via email at Compuserve address 76660,1024 if you
have found other C compilers that are compatible so that we may share
this information with other FORCE users.
In no event is C++ code compatible with FORCE!
As for assemblers, both Borland's Turbo Assembler (all versions) and
Microsoft's Macro Assembler (all versions) generate .OBJ files that
are compatible with Force.
Before we get started, let us just be sure where we stand. Assumed
knowledge for the next subsection, Interfacing With C, is that you are
learned and comfortable with the C language. We will not attempt to
teach any aspects of the language to you, other than for purposes of
review.
Interfacing with C
╒════════════════════════════════════════════════╕
│ Force Data Type C Data Type bytes │
│ --------------------------------------------- │
│ BYTE signed char 1 │
│ (U)INT(unsigned) int 2 │
│ (U)LONG(unsigned) long 4 │
│ DBL double 8 │
│ LOGICAL int 2 │
│ CHAR char │
│ [] variable │
│ --------------------------------------------- │
│ Table 8-1 - Force/C data types │
╘════════════════════════════════════════════════╛
C and Force share a very similar language and implementation
structure, a trait that allows the two to work together beautifully.
Force and C use the stack in the same way, use data segments in the
same way and implement functions and procedures nearly identically.
C's primitive data types match Force's very well (see Table 8-1).
Note the variable length in bytes of the data type CHAR. CHAR's C
equivalent is an array of characters, which is determined at compile
time.
The higher-level Force data types, such as a FILE, ALIAS and MEMO
have no real C equivalents, since they are in a real sense not actual
data types but rather "predefined data structures".
Passing Parameters to C Functions
C has various methods of passing parameters, just as Force does
(please reference MEMORY MANAGEMENT, Passing Parameters, for a
full discussion on how Force passes parameters). C has a CONST
keyword to signify that no modifications to the parameter are
allowed in the function. C also allows passing by value, but does
not use a VALUE keyword; it is implied. Finally, C allows passing
by reference, which is implemented by passing the address or
pointer as the parameter.
╒═════════════════════════════════════════════════════════════════════════╕
│ Parameter passing Will Pass use in Force use in C │
│ ----------------------------------------------------------------- │
│ by value a copy VALUE keyword implied │
│ │
│ by reference the address implied address │
│ or pointer │
│ constant value a copy, none const keyword │
│ but will not │
│ modify │
│ │
│ constant reference the address, CONST keyword const keyword │
│ but will not plus address │
│ modify or pointer │
│ │
│ ------------------------------------------------------------------ │
│ Table 8-2 - Passing parameters in Force and C │
╘═════════════════════════════════════════════════════════════════════════╛
Table 8-2 shows how Force and C implement the different parameter
passing methods. When passing by value, Force uses the VALUE keyword,
otherwise, passing by reference is implied in Force. The opposite is
true with C: C passes parameters by value, unless the address of the
parameter is passed. The one exception to this rule is for arrays. C
always passes the address of an array, where Force will allow you to
pass a copy of the array. Because Force CHAR types correspond to a
character array in C, you must pass CHAR parameters by reference. Of
course, the same applies to other types of arrays.
Let's take a look at some Force function prototypes, and try to write
corresponding C function prototypes. First, we'll try passing by
value.
FUNCTION UINT fn PROTOTYPE
PARAMETERS VALUE UINT x, VALUE DBL y
This function, if written in Force, returns an unsigned integer value,
and takes two parameters, an integer x and a double precision value y.
Because the parameters are values, the corresponding function
prototype in C would be:
unsigned int fn( int x, double y );
Seems simple enough. Now, let's try passing by reference. This
procedure will take a CHAR string and a long integer as its
parameters:
PROCEDURE proc PROTOTYPE
PARAMETERS CHAR string, ULONG x
Recall from MEMORY MANAGEMENT that passing by reference means passing
the address of (or passing a pointer to) the parameter. Thus, a
corresponding C function prototype would be expecting pointers to the
parameters:
void proc( char * string, long * x );
If we were to modify the procedure so that the parameters were to be
constant (non-modifiable by the procedure), we would add the CONST
keyword to our parameters:
PROCEDURE proc PROTOTYPE
PARAMETERS CONST CHAR string, CONST ULONG x
The corresponding C function would have the same changes:
void proc( const char * string, const long * x );
Now let's have a look at arrays. Remember that C always expects the
address of the (first element of the) array, therefore, you cannot
pass a copy of the array to C. This function takes an array of
integers and the number of integers in the array as its parameters.
It returns some integer value.
FUNCTION UINT fn PROTOTYPE
PARAMETERS UINT array[12], VALUE UINT length
The corresponding C function prototype looks like this:
int fn( int array[12], int length );
Finally, let's take a look at one Force/C discrepancy, a function that
returns a CHAR data type. The Force library function upper() takes a
CHAR parameter and returns its uppercase equivalent. The Force
function prototype for upper() is as follows (note the CONST because
the original string is not modified).
FUNCTION CHAR upper PROTOTYPE
PARAMETERS CONST CHAR source_string
You would expect the corresponding C prototype to return... Well,
what would it return? A CHAR in C and Force is an array of
single-byte characters, and you cannot return arrays in either
language. In C, you could have the function return a pointer to a new
string, but where would this new string come from? Actually, when a
function returns a CHAR parameter, Force passes the address of the
return destination as an "invisible" first parameter. This means that
the corresponding C function must receive a leading CHAR parameter.
This leading parameter is the location that the resulting string
should be stored. Thus, the corresponding C function prototype for
the upper() function is really:
void upper( char * destination, const char * source)
For this example, the result of upper() will be stored in the
destination parameter.
If you are planning to call Force functions from your C functions,
then this discussion simply applies in reverse. That is to say, the
Force function call is made "C style" in the C program even though
this may not agree with the usual Force prototype. Thus, if you were
to call Force's upper() function from C, you would have code similar
to the following:
void upper( char * dest, const char * source);
void proc( void )
{
char string1[] = "Hello, Force!"
char string2[50];
upper( string2, string1 );
printf("lowercase: %s\n", string1 );
printf("UPPERcase: %s\n", string2 );
}
Preliminary Rules of Thumb
Before we get into actually writing and linking C code to your Force
applications, there are a few rules that you must always heed when
working with C.
The application may consist of as much C code as you like, but must
start at force_main(), and not C's main() function. If most of the
application will be in C, then you may call a main()-like function as
your first or only command in force_main(). The main()-like function
may then call Force functions as described above.
NOTE: Always compile your C code with the large memory model.
If you are using functions in the Microsoft C library, or are using
any third party library that does, be sure to call setup_micro()
as your first command in force_main(). Also be sure to link in the
Microsoft start-up code, MICRO5.OBJ or MICRO6.OBJ depending on
which version of the compiler you have (these start-up modules are in
your \FORCE\LIB directory).
If you are using any double precision values in your Microsoft C
code, be sure that you link in the Microsoft floating-point
startup code, MFP5.OBJ (will work for Microsoft 6.0, also).
If you are using functions in the Borland C library, or are using any
third party library that does, be sure to call setup_turboc() as
your first command in force_main(). Also be sure to link in the
Borland start-up code, TURBOC.OBJ.
Now let's get on with some examples.
Interfacing Force with Your Own C Functions
If you are writing your own C functions to be called from Force, you
can often write them without calling any functions from the standard C
library provided with your C compiler. This is often possible because
Force already provides similar functions for most of the functions in
the C library. For example, standard C libraries provide several
functions for low-level file I/O. Force provides those same
functions, as you can see in Table 8-3.
╒═══════════════════════════════════════════════════╕
│ Table 8-3. File I/O functions │
│ -------------------------------- │
│ C function Force function │
│ ---------- -------------- │
│ open() fb_open() │
│ close() fb_close() │
│ read() fb_read() │
│ write() fb_write() │
│ lseek() fb_seek() │
│ eof() fb_eof() │
│ │
╘═══════════════════════════════════════════════════╛
To see this in action, here's a function written in C that uses only
Force library functions to copy a file from one filename to another.
It uses a dynamically allocated memory buffer to read and write bytes
from the first file to the second. Note that C does not require us to
write function prototypes, but it is considered good programming
practice, as it helps the C compiler perform parameter type-checking.
/*
* copyfile.c -- Example code: Calling Force library
* functions from a C source module.
*/
typedef int LOGICAL; /* C has no LOGICAL data type,
** so we roll our own. */
/*
* Here are the function prototypes for the Force library
* functions that copy_file() will be using.
*/
logical fb_open( int *handle, const char *filename,
int mode );
void fb_close( int handle );
unsigned int fb_read( int handle, void * buffer,
unsigned int nbytes );
unsigned int fb_write(int handle, void * buffer,
unsigned int nbytes );
void far * malloc( unsigned int nbytes);
void free( void * ptr );
/*
* File open modes.
*/
#define B_READ 1
#define B_CREAD_WRITE 2
#define B_CREATE 2
#define B_WRITE 3
#define B_READ_WRITE 4
#define B_CREAD 5
#define B_CWRITE 6
/*
* procedure: copy_file()
*
* description: copy_file() takes two filenames, and copies
* the first into the second. copy_file() returns False
* if there is an error (such as no dynamic memory left,
* or file does not exist).
*/
logical copy_file( char * source, char * dest ) {
void far * buffer;
int in_handle;
int out_handle;
int bytes_read;
/*
* Let's open the files, and return False if we can't.
*/
if (! fb_open( &in_handle, source, B_READ ))
return( 0 ); /* return False */
if (! fb_open( &out_handle, dest, B_CWRITE ))
{
fb_close( in_handle );
return( 0 ); /* return False */
}
/*
* Now we allocate a 5K buffer and return False if
* we cannot get it.
*/
buffer = malloc( 5120 );
if (! buffer) {
fb_close( in_handle );
fb_close( out_handle );
return( 0 ); /* return False */
}
do {
bytes_read = fb_read( in_handle, buffer, 5120 );
fb_write( out_handle, buffer, bytes_read );
} while( bytes_read == 5120 );
fb_close( in_handle );
fb_close( out_handle );
return( 1 ); /* return True */
}
/*-- EOF: copyfile.c ----------------------------------*/
Now let's write a test program in Force that uses copy_file(). Our
test program will simply have copy_file() copy the executable code
to a different name. We'll get the name of the executable from the
Force library function get_exec().
*
* test.prg - Tests the copy_file() function
*
#include io.hdr
#include system.hdr
FUNCTION LOGICAL copy_file PROTOTYPE
PARAMETERS CONST CHAR source,CONST CHAR dest
PROCEDURE force_main
VARDEF
CHAR(128) This_File
ENDDEF
This_File = get_exec( )
? "Copying " + This_File + "to output.exe"
IF .NOT. copy_file( This_File, "output.exe" )
? "Cannot copy file!"
ELSE
? "Done!"
ENDIF
ENDPRO
*-- EOF: test.prg --------------------------------*
To make sure that everything works, save the C source code as
COPYFILE.C and the above Force test program as TEST.PRG. Compile
TEST.PRG module using the following line:
Force test.prg
Now compile the COPYFILE.C module. If you're using Microsoft C,
version 5.0 or 6.0, use the following line:
cl /AL /Gs copyfile.c
If you're using Borland's Turbo C or Borland C, compile it with this
line:
tcc -ml -c copyfile.c
Now you should have two more files in your directory, TEST.OBJ and
COPYFILE.OBJ and we need to link them together with the Force library.
If you're using Microsoft C, use this line:
link test copyfile,test,Nul,force ;
For Borland:
TLINK test copyfile,test,nul,force ;
Run the TEST.EXE program, and you'll see the lines:
Copying test.exe to output.exe
done
Interfacing with C libraries
Let's get a little more complicated now by using the functions in the
C library. We'll modify our C source example to change the date of
the destination file to the date of the source file. To do this,
we'll have to use the functions in the standard C library to read and
write file dates and times. Unfortunately, the names of these
functions differ between Borland and Microsoft libraries, but we can
get around this problem using the C preprocessor. Here's the new code
for COPYFILE.C.
/*
* copyfile.c -- Example code: Calling Force library
* functions from a C source module.
*/
#include <io.h>
#include <dos.h>
typedef int LOGICAL; /* C has no LOGICAL data type,
** so we roll our own. */
/*
* Here are the function prototypes for the Force library
* functions that copy_file() will be using.
*/
LOGICAL fb_open( int *handle, const char *filename,
int mode );
void fb_close( int handle );
unsigned int fb_read( int handle, void * buffer,
unsigned int nbytes );
unsigned int fb_write(int handle, void * buffer,
unsigned int nbytes );
void far * malloc( unsigned int nbytes);
void free( void * ptr );
/*
* File open modes.
*/
#define B_READ 1
#define B_CREAD_WRITE 2
#define B_CREATE 2
#define B_WRITE 3
#define B_READ_WRITE 4
#define B_CREAD 5
#define B_CWRITE 6
/*
* procedure: copy_file()
*
* description: copy_file() takes two filenames, and copies
* the first into the second. copy_file() returns False
* if there is an error (such as no dynamic memory left,
* or file does not exist).
*/
logical copy_file( char * source, char * dest ) {
void far * buffer;
int in_handle;
int out_handle;
int bytes_read;
#ifdef _MSC_VER
unsigned file_date, file_time;
#else
struct ftime file_time;
#endif
/*
* Let's open the files, and return False if we can't.
*/
if (! fb_open( &in_handle, source, B_READ ))
return( 0 ); /* return False */
if (! fb_open( &out_handle, dest, B_CWRITE )) {
fb_close( in_handle );
return( 0 ); /* return False */
}
/*
* Here we get the time and date of the source file.
*/
# ifdef _MSC_VER
_dos_getftime( in_handle, &file_date,
&file_time );
# else
getftime( in_handle, &file_time );
# endif
/*
* Now we allocate a 5K buffer and return False if
* we cannot get it.
*/
buffer = malloc( 5120 );
if (! buffer) {
fb_close( in_handle );
fb_close( out_handle );
return( 0 ); /* return False */
}
do {
bytes_read = fb_read( in_handle, buffer, 5120 );
fb_write( out_handle, buffer, bytes_read );
} while( bytes_read == 5120 );
fb_close( in_handle );
/*
* Before we close the output file, we change its
* date and time to that of the source file.
*/
# ifdef _MSC_VER
_dos_setftime( out_handle, file_date,
file_time );
# else
setftime( out_handle, &file_time );
# endif
fb_close( out_handle );
return( 1 ); /* return True */
}
/*-- EOF: copyfile.c ----------------------------------*/
The only change that we need to make to the TEST.PRG module is to
call setup_micro() for Microsoft C or setup_turboc() for Borland's
C. If you are using the Borland C or Turbo C compiler, you'll
need to #define the macro TURBOC before #including MIXED.HDR.
* test.prg - Tests the copy_file() function *
#include io.hdr
#include system.hdr
* #define TURBOC // for Turbo C users
#include mixed.hdr
FUNCTION LOGICAL copy_file PROTOTYPE
PARAMETERS CONST CHAR source, CONST CHAR dest
PROCEDURE force_main
VARDEF
CHAR(128) This_File
ENDDEF
#ifdef TURBOC
setup_turboc()
#else
setup_micro()
#endif
This_File = get_exec( )
? "Copying " + This_File + "to output.exe"
IF .NOT. copy_file( This_File, "output.exe" )
? "Cannot copy file!"
ELSE
? "Done!"
ENDIF
ENDPRO
*-- EOF: test.prg --------------------------------
Compile the files as you did before. Linking them will be a little
more complex. Recall that when you link with a C library, you have to
link in not only the object modules for the code that you have
written, but you must also link in the Force library, the large memory
model of the C library, and the start-up code for the C library. For
this example, if you are using Microsoft C version 5.0, use this line:
LINK test copyfile c:\force\lib\micro5, test, Nul, FORCE.LIB llibce.lib;
If you are using Microsoft C version 6.0 change micro5 to micro6.
For Turbo C version 2.0 through 4.0, use this line:
TLINK /n test copyfile c:\force\lib\turboc, test, Nul, FORCE.LIB cl.lib
┌───────────────────────────────────────┐
│ Linking C Third Party Libs into FORCE │
└───────────────────────────────────────┘
FORCE requires the C Calling Convention
---------------------------------------
The single incompatibility for FORCE and third party C libraries is
that FORCE is unable to use the PASCAL calling convention. A major
element of the PASCAL convention involves the absence of any leading
underscore on symbol names. Because the FORCE compiler follows the C
calling convention and automatically prepends a leading underscore, code
that it generates is incompatible with compilers that follow the PASCAL
convention. Most prominent C third parties (such as the TurboPower libs)
customarily ship with source and it is generally simple simply to remake
the third party lib with the C convention specified, rather than PASCAL.
Writing "Wrappers" for Third Party C Functions
----------------------------------------------
The C - oriented question heard most often at Tech Support is "what must
I do to link in my familiar C third party libraries?". Generally, the
answer is "not much", because FORCE and C approach matters very similarly
in many areas. Most of the work involves the simple writing of a
"wrapper" function to send parameters to and get return values from the C
third party function in the proper order and form.
As an example of linking in standard and well behaved C third parties,
please review the following discussion of the method for writing a
simple Force "wrapper" that allows the functions contained in the
popular and versatile FUNCKy library to be called easily and simply from
Force:
Let's use the FUNCky "dtoi" procedure to convert a date string to integer
values. Now, Force and FUNCky use the large model, far calls, and the so
called "C calling convention". This last means, among other things, that
the library routine names all begin with an underscore ("_"). Force
prepends the underscore to your symbols so that "dtoi()" becomes "_dtoi()
in the generated object file, thus matching the library names. In the
FUNCky surface/core library hierarchy this means that the surface library
routine names must have the leading underscore. The surface library
handles the language interface requirements & then calls the appropriate
core library routines. In order to distinguish the surface and core
routine names, the core names have an additional leading underscore, ie-
in the core library our "dtoi" procedure becomes "__dtoi". What does all
this mean to us? The first "secret" is that we must add one underscore
to the routine name and Force adds the other. Here's how to do it:
*- PROGRAM DTOITEST.PRG
*- inspection of the FUNCky procedure description reveals that we need
these * parameters in this order. Note that we specify the length of
the date char * string and note also the leading underscore applied to
"dtoi"
procedure _dtoi prototype parameters const char(8) stringDate, int
iiYear, int iiYmonth, int iiDay
*- OK, now let's test it..
procedure force_main
vardef
char(8) cDate
int iYear,iMonth,iDay
enddef
cDate = "06/09/94"
_dtoi(sDate, iYear, iMonth, iDay)
? iYear ? iMonth ? iDay
endpro
The second "secret" is the use of the qualifier "const" before the input
string name in the prototype. This allows you to pass a literal string as
well as a variable name, so you could have written
_dtoi("06/09/94",iYear,iMonth,iDay).
Now compile & link with the core library:
FL dtoitest,,,f:\funcky\lib\funcky2c force
You could always add the FUNCky lib directory to your SET LIB=...
statement and link with "FL dtoitest" but we prefer to control the link
libraries manually. This is most easily done via a makefile, of course.
Another challenge in converting Funcky2 to Force seems to be dealing with
the differences in parameter (data) type syntax. We just need to pass all
values by reference and remember to use the "const" qualifier when we need
to allow the passage of a literal value. (There is also the problem that
the Clipper Julian date base is apparently different from that of Force -
Force internal dates are the number of days since day 1, year 0; we haven't
at this point researched the Clipper date base. We therefore feel it
best to avoid the use of julian stuff unless it is entirely "internal"
to our libraries.)
The FUNCKy2C (core library) uses C style parameter passing and is
therefore sometimes at odds with Force _function_ parameter passing. For
example, the Funcky2 function _catstr is defined as:
char * _catstr ( str1, str2, buffer )
this would correspond to the Force PROCEDURE (not function)
procedure _catstring prototype
params const char cVar1, const char cVar2, char cBuffer
This is because, for FUNCTIONS, Force pushes a final "extra" parameter
onto the stack after the "visible" function parameters. This extra
pointer points to the return value destination. So, in order to turn the
Funcky2 "function" into a Force function we must use the _catstr prototype
for Force and write a wrapper:
procedure _catstr prototype
params const char cVar1, const char cVar2, char cBuffer
function char catstring
params const char cVar1, const char CVar2
vardef
char buffer
enddef
_catstring(cVar1,cVar2,buffer)
return buffer
endfunc
We would stick this module in our "surface" library for Force & then we
could just call catstring() whenever we want:
? catstring("1234","ABCdef") or newString:= catstr("Holy"," Moly!")
This is a trivial example, of course, because we don't need a function to
concatenate strings but serves to illustrate the principle.
Watch for things like the __itod() function - note the extra underscore.
This little tidbit (the need for the extra underscore) is burried in the
docs and is the sort of thing that can lead to much frustration and
solvent abuse.
The foregoing is a necessarily incomplete discussion of a fascinating
area: the close compatibility of FORCE and C code which makes nearly all
well-behaved C libraries immediately callable from FORCE with only a
little effort!